#include "wx/wxprec.h"

#if wxUSE_LISTBOX

#include "wx/listbox.h"
#include "wx/gtk/private.h"
#include "wx/gtk/treeentry_gtk.h"

#if wxUSE_TOOLTIPS
#include "wx/tooltip.h"
#endif

#include <gdk/gdk.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>

//-----------------------------------------------------------------------------
// data
//-----------------------------------------------------------------------------

extern bool           g_blockEventsOnDrag;
extern bool           g_blockEventsOnScroll;

//-----------------------------------------------------------------------------
// Macro to tell which row the strings are in (1 if native checklist, 0 if not)
//-----------------------------------------------------------------------------

#if wxUSE_CHECKLISTBOX
#   define WXLISTBOX_DATACOLUMN_ARG(x)  (x->m_hasCheckBoxes ? 1 : 0)
#else
#   define WXLISTBOX_DATACOLUMN_ARG(x)  (0)
#endif // wxUSE_CHECKLISTBOX

#define WXLISTBOX_DATACOLUMN    WXLISTBOX_DATACOLUMN_ARG(this)

//-----------------------------------------------------------------------------
// "row-activated"
//-----------------------------------------------------------------------------

extern "C" {
  static void
  gtk_listbox_row_activated_callback( GtkTreeView        *treeview,
                                      GtkTreePath        *path,
                                      GtkTreeViewColumn  *col,
                                      wxListBox          *listbox ) {
    if( g_isIdle ) {
      wxapp_install_idle_handler();
    }
    if( g_blockEventsOnDrag ) {
      return;
    }
    if( g_blockEventsOnScroll ) {
      return;
    }
    // This is triggered by either a double-click or a space press
    int sel = gtk_tree_path_get_indices( path )[0];
    wxCommandEvent event( wxEVT_COMMAND_LISTBOX_DOUBLECLICKED, listbox->GetId() );
    event.SetEventObject( listbox );
    if( listbox->IsSelected( sel ) ) {
      GtkTreeEntry* entry = listbox->GtkGetEntry( sel );
      if( entry ) {
        event.SetInt( sel );
        event.SetString( wxConvUTF8.cMB2WX( gtk_tree_entry_get_label( entry ) ) );
        if( listbox->HasClientObjectData() ) {
          event.SetClientObject( ( wxClientData* ) gtk_tree_entry_get_userdata( entry ) );
        } else if( listbox->HasClientUntypedData() ) {
          event.SetClientData( gtk_tree_entry_get_userdata( entry ) );
        }
        g_object_unref( entry );
      } else {
        wxLogSysError( wxT( "Internal error - could not get entry for double-click" ) );
        event.SetInt( -1 );
      }
    } else {
      event.SetInt( -1 );
    }
    listbox->GetEventHandler()->ProcessEvent( event );
  }
}

//-----------------------------------------------------------------------------
// "key_press_event"
//-----------------------------------------------------------------------------

extern "C" {
  static gint
  gtk_listbox_key_press_callback( GtkWidget *widget,
                                  GdkEventKey *gdk_event,
                                  wxListBox *listbox ) {
    if( g_blockEventsOnDrag ) {
      return FALSE;
    }
    if( ( gdk_event->keyval == GDK_Tab ) || ( gdk_event->keyval == GDK_ISO_Left_Tab ) ) {
      wxNavigationKeyEvent new_event;
      /* GDK reports GDK_ISO_Left_Tab for SHIFT-TAB */
      new_event.SetDirection( ( gdk_event->keyval == GDK_Tab ) );
      /* CTRL-TAB changes the (parent) window, i.e. switch notebook page */
      new_event.SetWindowChange( ( gdk_event->state & GDK_CONTROL_MASK ) );
      new_event.SetCurrentFocus( listbox );
      if( listbox->GetEventHandler()->ProcessEvent( new_event ) ) {
        return TRUE;
      }
    }
    return FALSE;
  }
}

//-----------------------------------------------------------------------------
// "changed"
//-----------------------------------------------------------------------------

extern "C" {
  static void
  gtk_listitem_changed_callback( GtkTreeSelection* selection, wxListBox *listbox ) {
    if( g_blockEventsOnDrag ) {
      return;
    }
    if( listbox->m_blockEvent ) {
      return;
    }
    wxCommandEvent event( wxEVT_COMMAND_LISTBOX_SELECTED, listbox->GetId() );
    event.SetEventObject( listbox );
    if( listbox->HasFlag( wxLB_MULTIPLE ) || listbox->HasFlag( wxLB_EXTENDED ) ) {
      wxArrayInt selections;
      listbox->GetSelections( selections );
      if( selections.GetCount() == 0 ) {
        // indicate that this is a deselection
        event.SetExtraLong( 0 );
        event.SetInt( -1 );
        listbox->GetEventHandler()->ProcessEvent( event );
        return;
      } else {
        // indicate that this is a selection
        event.SetExtraLong( 1 );
        event.SetInt( selections[0] );
        listbox->GetEventHandler()->ProcessEvent( event );
      }
    } else {
      int index = listbox->GetSelection();
      if( index == wxNOT_FOUND ) {
        // indicate that this is a deselection
        event.SetExtraLong( 0 );
        event.SetInt( -1 );
        listbox->GetEventHandler()->ProcessEvent( event );
        return;
      } else {
        GtkTreeEntry* entry = listbox->GtkGetEntry( index );
        // indicate that this is a selection
        event.SetExtraLong( 1 );
        event.SetInt( index );
        event.SetString( wxConvUTF8.cMB2WX( gtk_tree_entry_get_label( entry ) ) );
        if( listbox->HasClientObjectData() )
          event.SetClientObject(
            ( wxClientData* ) gtk_tree_entry_get_userdata( entry )
          );
        else if( listbox->HasClientUntypedData() ) {
          event.SetClientData( gtk_tree_entry_get_userdata( entry ) );
        }
        listbox->GetEventHandler()->ProcessEvent( event );
        g_object_unref( entry );
      }
    }
  }
}

//-----------------------------------------------------------------------------
// GtkTreeEntry destruction (to destroy client data)
//-----------------------------------------------------------------------------

extern "C" {
  static void gtk_tree_entry_destroy_cb( GtkTreeEntry* entry,
                                         wxListBox* listbox ) {
    if( listbox->HasClientObjectData() ) {
      gpointer userdata = gtk_tree_entry_get_userdata( entry );
      if( userdata ) {
        delete( wxClientData * )userdata;
      }
    }
  }
}

//-----------------------------------------------------------------------------
// Sorting callback (standard CmpNoCase return value)
//-----------------------------------------------------------------------------

extern "C" {
  static gint gtk_listbox_sort_callback( GtkTreeModel *model,
                                         GtkTreeIter  *a,
                                         GtkTreeIter  *b,
                                         wxListBox    *listbox ) {
    GtkTreeEntry* entry;
    GtkTreeEntry* entry2;
    gtk_tree_model_get( GTK_TREE_MODEL( listbox->m_liststore ),
                        a,
                        WXLISTBOX_DATACOLUMN_ARG( listbox ),
                        &entry, -1 );
    gtk_tree_model_get( GTK_TREE_MODEL( listbox->m_liststore ),
                        b,
                        WXLISTBOX_DATACOLUMN_ARG( listbox ),
                        &entry2, -1 );
    wxCHECK_MSG( entry, 0, wxT( "Could not get entry" ) );
    wxCHECK_MSG( entry2, 0, wxT( "Could not get entry2" ) );
    //We compare collate keys here instead of calling g_utf8_collate
    //as it is rather slow (and even the docs reccommend this)
    int ret = strcasecmp( gtk_tree_entry_get_collate_key( entry ),
                          gtk_tree_entry_get_collate_key( entry2 ) );
    g_object_unref( entry );
    g_object_unref( entry2 );
    return ret;
  }
}

//-----------------------------------------------------------------------------
// Searching callback (TRUE == not equal, FALSE == equal)
//-----------------------------------------------------------------------------

extern "C" {
  static gboolean gtk_listbox_searchequal_callback( GtkTreeModel* model,
      gint column,
      const gchar* key,
      GtkTreeIter* iter,
      wxListBox* listbox ) {
    GtkTreeEntry* entry;
    gtk_tree_model_get( GTK_TREE_MODEL( listbox->m_liststore ),
                        iter,
                        WXLISTBOX_DATACOLUMN_ARG( listbox ),
                        &entry, -1 );
    wxCHECK_MSG( entry, 0, wxT( "Could not get entry" ) );
    wxGtkString keycollatekey( g_utf8_collate_key( key, -1 ) );
    int ret = strcasecmp( keycollatekey,
                          gtk_tree_entry_get_collate_key( entry ) );
    g_object_unref( entry );
    return ret != 0;
  }
}

//-----------------------------------------------------------------------------
// wxListBox
//-----------------------------------------------------------------------------

IMPLEMENT_DYNAMIC_CLASS( wxListBox, wxControl )

// ----------------------------------------------------------------------------
// construction
// ----------------------------------------------------------------------------

void wxListBox::Init() {
  m_treeview = ( GtkTreeView* ) NULL;
  #if wxUSE_CHECKLISTBOX
  m_hasCheckBoxes = false;
  #endif // wxUSE_CHECKLISTBOX
}

bool wxListBox::Create( wxWindow *parent, wxWindowID id,
                        const wxPoint &pos, const wxSize &size,
                        const wxArrayString& choices,
                        long style, const wxValidator& validator,
                        const wxString &name ) {
  wxCArrayString chs( choices );
  return Create( parent, id, pos, size, chs.GetCount(), chs.GetStrings(),
                 style, validator, name );
}

bool wxListBox::Create( wxWindow *parent, wxWindowID id,
                        const wxPoint &pos, const wxSize &size,
                        int n, const wxString choices[],
                        long style, const wxValidator& validator,
                        const wxString &name ) {
  m_needParent = true;
  m_acceptsFocus = true;
  m_blockEvent = false;
  if( !PreCreation( parent, pos, size ) ||
      !CreateBase( parent, id, pos, size, style, validator, name ) ) {
    wxFAIL_MSG( wxT( "wxListBox creation failed" ) );
    return false;
  }
  m_widget = gtk_scrolled_window_new( ( GtkAdjustment* ) NULL, ( GtkAdjustment* ) NULL );
  if( style & wxLB_ALWAYS_SB ) {
    gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( m_widget ),
                                    GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS );
  } else {
    gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( m_widget ),
                                    GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC );
  }
  GtkScrolledWindowSetBorder( m_widget, style );
  m_treeview = GTK_TREE_VIEW( gtk_tree_view_new() );
  //wxListBox doesn't have a header :)
  //NB: If enabled SetFirstItem doesn't work correctly
  gtk_tree_view_set_headers_visible( m_treeview, FALSE );
  #if wxUSE_CHECKLISTBOX
  if( m_hasCheckBoxes ) {
    ( ( wxCheckListBox* )this )->DoCreateCheckList();
  }
  #endif // wxUSE_CHECKLISTBOX
  // Create the data column
  gtk_tree_view_insert_column_with_attributes( m_treeview, -1, "",
      gtk_cell_renderer_text_new(),
      "text",
      WXLISTBOX_DATACOLUMN, NULL );
  // Now create+set the model (GtkListStore) - first argument # of columns
  #if wxUSE_CHECKLISTBOX
  if( m_hasCheckBoxes )
    m_liststore = gtk_list_store_new( 2, G_TYPE_BOOLEAN,
                                      GTK_TYPE_TREE_ENTRY );
  else
  #endif
    m_liststore = gtk_list_store_new( 1, GTK_TYPE_TREE_ENTRY );
  gtk_tree_view_set_model( m_treeview, GTK_TREE_MODEL( m_liststore ) );
  g_object_unref( m_liststore ); //free on treeview destruction
  // Disable the pop-up textctrl that enables searching - note that
  // the docs specify that even if this disabled (which we are doing)
  // the user can still have it through the start-interactive-search
  // key binding...either way we want to provide a searchequal callback
  // NB: If this is enabled a doubleclick event (activate) gets sent
  //     on a successful search
  gtk_tree_view_set_search_column( m_treeview, WXLISTBOX_DATACOLUMN );
  gtk_tree_view_set_search_equal_func( m_treeview,
                                       ( GtkTreeViewSearchEqualFunc ) gtk_listbox_searchequal_callback,
                                       this,
                                       NULL );
  gtk_tree_view_set_enable_search( m_treeview, FALSE );
  GtkTreeSelection* selection = gtk_tree_view_get_selection( m_treeview );
  g_signal_connect_after( selection, "changed",
                          G_CALLBACK( gtk_listitem_changed_callback ), this );
  GtkSelectionMode mode;
  if( style & wxLB_MULTIPLE ) {
    mode = GTK_SELECTION_MULTIPLE;
  } else if( style & wxLB_EXTENDED ) {
    mode = GTK_SELECTION_EXTENDED;
  } else {
    // if style was 0 set single mode
    m_windowStyle |= wxLB_SINGLE;
    mode = GTK_SELECTION_SINGLE;
  }
  gtk_tree_selection_set_mode( selection, mode );
  // Handle sortable stuff
  if( style & wxLB_SORT ) {
    // Setup sorting in ascending (wx) order
    gtk_tree_sortable_set_sort_column_id( GTK_TREE_SORTABLE( m_liststore ),
                                          WXLISTBOX_DATACOLUMN,
                                          GTK_SORT_ASCENDING );
    // Set the sort callback
    gtk_tree_sortable_set_sort_func( GTK_TREE_SORTABLE( m_liststore ),
                                     WXLISTBOX_DATACOLUMN,
                                     ( GtkTreeIterCompareFunc ) gtk_listbox_sort_callback,
                                     this, //userdata
                                     NULL //"destroy notifier"
                                   );
  }
  gtk_container_add( GTK_CONTAINER( m_widget ), GTK_WIDGET( m_treeview ) );
  gtk_widget_show( GTK_WIDGET( m_treeview ) );
  m_focusWidget = GTK_WIDGET( m_treeview );
  wxListBox::DoInsertItems( wxArrayString( n, choices ), 0 ); // insert initial items
  // generate dclick events
  g_signal_connect_after( m_treeview, "row-activated",
                          G_CALLBACK( gtk_listbox_row_activated_callback ), this );
  // for panel navigation
  g_signal_connect( m_treeview, "key_press_event",
                    G_CALLBACK( gtk_listbox_key_press_callback ),
                    this );
  m_parent->DoAddChild( this );
  PostCreation( size );
  SetInitialSize( size ); // need this too because this is a wxControlWithItems
  return true;
}

wxListBox::~wxListBox() {
  m_hasVMT = false;
  Clear();
}

// ----------------------------------------------------------------------------
// adding items
// ----------------------------------------------------------------------------

void wxListBox::GtkInsertItems( const wxArrayString& items,
                                void** clientData, unsigned int pos ) {
  wxCHECK_RET( m_treeview != NULL, wxT( "invalid listbox" ) );
  InvalidateBestSize();
  // Create and set column ids and GValues
  unsigned int nNum = items.GetCount();
  unsigned int nCurCount = wxListBox::GetCount();
  wxASSERT_MSG( pos <= nCurCount, wxT( "Invalid index passed to wxListBox" ) );
  GtkTreeIter* pIter = NULL; // append by default
  GtkTreeIter iter;
  if( pos != nCurCount ) {
    gboolean res = gtk_tree_model_iter_nth_child(
                     GTK_TREE_MODEL( m_liststore ),
                     &iter, NULL, //NULL = parent = get first
                     ( int )pos );
    if( !res ) {
      wxLogSysError( wxT( "internal wxListBox error in insertion" ) );
      return;
    }
    pIter = &iter;
  }
  for( unsigned int i = 0; i < nNum; ++i ) {
    wxString label = items[i];
    GtkTreeEntry* entry = gtk_tree_entry_new();
    gtk_tree_entry_set_label( entry, wxGTK_CONV( label ) );
    gtk_tree_entry_set_destroy_func( entry,
                                     ( GtkTreeEntryDestroy )gtk_tree_entry_destroy_cb,
                                     this );
    if( clientData ) {
      gtk_tree_entry_set_userdata( entry, clientData[i] );
    }
    GtkTreeIter itercur;
    gtk_list_store_insert_before( m_liststore, &itercur, pIter );
    #if wxUSE_CHECKLISTBOX
    if( m_hasCheckBoxes ) {
      gtk_list_store_set( m_liststore, &itercur,
                          0, FALSE, //FALSE == not toggled
                          1, entry, -1 );
    } else
    #endif
      gtk_list_store_set( m_liststore, &itercur,
                          0, entry, -1 );
    g_object_unref( entry ); //liststore always refs :)
  }
}

void wxListBox::DoInsertItems( const wxArrayString& items, unsigned int pos ) {
  wxCHECK_RET( IsValidInsert( pos ), wxT( "invalid index in wxListBox::InsertItems" ) );
  GtkInsertItems( items, NULL, pos );
}

int wxListBox::DoAppend( const wxString& item ) {
  wxCHECK_MSG( m_treeview != NULL, -1, wxT( "invalid listbox" ) );
  InvalidateBestSize();
  GtkTreeEntry* entry = gtk_tree_entry_new();
  gtk_tree_entry_set_label( entry, wxGTK_CONV( item ) );
  gtk_tree_entry_set_destroy_func( entry,
                                   ( GtkTreeEntryDestroy )gtk_tree_entry_destroy_cb,
                                   this );
  GtkTreeIter itercur;
  gtk_list_store_insert_before( m_liststore, &itercur, NULL );
  #if wxUSE_CHECKLISTBOX
  if( m_hasCheckBoxes ) {
    gtk_list_store_set( m_liststore, &itercur,
                        0, FALSE, //FALSE == not toggled
                        1, entry, -1 );
  } else
  #endif
    gtk_list_store_set( m_liststore, &itercur,
                        0, entry, -1 );
  g_object_unref( entry ); //liststore always refs :)
  GtkTreePath* path = gtk_tree_model_get_path(
                        GTK_TREE_MODEL( m_liststore ),
                        &itercur );
  gint* pIntPath = gtk_tree_path_get_indices( path );
  if( pIntPath == NULL ) {
    wxLogSysError( wxT( "internal wxListBox error in insertion" ) );
    return wxNOT_FOUND;
  }
  int index = pIntPath[0];
  gtk_tree_path_free( path );
  return index;
}

void wxListBox::DoSetItems( const wxArrayString& items,
                            void **clientData ) {
  Clear();
  GtkInsertItems( items, clientData, 0 );
}

// ----------------------------------------------------------------------------
// deleting items
// ----------------------------------------------------------------------------

void wxListBox::Clear() {
  wxCHECK_RET( m_treeview != NULL, wxT( "invalid listbox" ) );
  InvalidateBestSize();
  gtk_list_store_clear( m_liststore ); /* well, THAT was easy :) */
}

void wxListBox::Delete( unsigned int n ) {
  wxCHECK_RET( m_treeview != NULL, wxT( "invalid listbox" ) );
  InvalidateBestSize();
  GtkTreeIter iter;
  gboolean res = gtk_tree_model_iter_nth_child(
                   GTK_TREE_MODEL( m_liststore ),
                   &iter, NULL, //NULL = parent = get first
                   n
                 );
  wxCHECK_RET( res, wxT( "wrong listbox index" ) );
  //this returns false if iter is invalid (i.e. deleting item
  //at end) but since we don't use iter, well... :)
  gtk_list_store_remove( m_liststore, &iter );
}

// ----------------------------------------------------------------------------
// get GtkTreeEntry from position (note: you need to g_unref it if valid)
// ----------------------------------------------------------------------------

struct _GtkTreeEntry* wxListBox::GtkGetEntry( int n ) const {
  GtkTreeIter iter;
  gboolean res = gtk_tree_model_iter_nth_child(
                   GTK_TREE_MODEL( m_liststore ),
                   &iter, NULL, //NULL = parent = get first
                   n );

  if( !res ) {
    wxLogDebug( wxT( "gtk_tree_model_iter_nth_child failed\n" )
                wxT( "Passed in value was:[%i]  List size:[%u]" ),
                n, wxListBox::GetCount() );
    return NULL;
  }


  GtkTreeEntry* entry = NULL;
  gtk_tree_model_get( GTK_TREE_MODEL( m_liststore ), &iter,
                      WXLISTBOX_DATACOLUMN, &entry, -1 );

  return entry;
}

// ----------------------------------------------------------------------------
// client data
// ----------------------------------------------------------------------------

void* wxListBox::DoGetItemClientData( unsigned int n ) const {
  wxCHECK_MSG( IsValid( n ), NULL,
               wxT( "Invalid index passed to GetItemClientData" ) );
  GtkTreeEntry* entry = GtkGetEntry( n );
  wxCHECK_MSG( entry, NULL, wxT( "could not get entry" ) );
  void* userdata = gtk_tree_entry_get_userdata( entry );
  g_object_unref( entry );
  return userdata;
}

wxClientData* wxListBox::DoGetItemClientObject( unsigned int n ) const {
  return ( wxClientData* ) wxListBox::DoGetItemClientData( n );
}

void wxListBox::DoSetItemClientData( unsigned int n, void* clientData ) {
  wxCHECK_RET( IsValid( n ),
               wxT( "Invalid index passed to SetItemClientData" ) );
  GtkTreeEntry* entry = GtkGetEntry( n );
  wxCHECK_RET( entry, wxT( "could not get entry" ) );
  gtk_tree_entry_set_userdata( entry, clientData );
  g_object_unref( entry );
}

void wxListBox::DoSetItemClientObject( unsigned int n, wxClientData* clientData ) {
  // wxItemContainer already deletes data for us
  wxListBox::DoSetItemClientData( n, ( void* ) clientData );
}

// ----------------------------------------------------------------------------
// string list access
// ----------------------------------------------------------------------------

void wxListBox::SetString( unsigned int n, const wxString &string ) {
  wxCHECK_RET( IsValid( n ), wxT( "invalid index in wxListBox::SetString" ) );
  wxCHECK_RET( m_treeview != NULL, wxT( "invalid listbox" ) );
  GtkTreeEntry* entry = GtkGetEntry( n );
  wxCHECK_RET( entry, wxT( "wrong listbox index" ) );
  wxString label = string;
  // Don't call the selection event handler if we only changed the item label
  m_blockEvent = true;
  // RN: This may look wierd but the problem is that the TreeView
  // doesn't resort or update when changed above and there is no real
  // notification function...
  void* userdata = gtk_tree_entry_get_userdata( entry );
  gtk_tree_entry_set_userdata( entry, NULL ); //don't delete on destroy
  g_object_unref( entry );
  bool bWasSelected = wxListBox::IsSelected( n );
  wxListBox::Delete( n );
  wxArrayString aItems;
  aItems.Add( label );
  GtkInsertItems( aItems, &userdata, n );
  if( bWasSelected ) {
    wxListBox::GtkSetSelection( n, true, true );
  }
  m_blockEvent = false;
}

wxString wxListBox::GetString( unsigned int n ) const {
  wxCHECK_MSG( m_treeview != NULL, wxEmptyString, wxT( "invalid listbox" ) );
  GtkTreeEntry* entry = GtkGetEntry( n );
  wxCHECK_MSG( entry, wxEmptyString, wxT( "wrong listbox index" ) );
  wxString label = wxGTK_CONV_BACK( gtk_tree_entry_get_label( entry ) );
  g_object_unref( entry );
  return label;
}

unsigned int wxListBox::GetCount() const {
  wxCHECK_MSG( m_treeview != NULL, 0, wxT( "invalid listbox" ) );
  return ( unsigned int )gtk_tree_model_iter_n_children( GTK_TREE_MODEL( m_liststore ), NULL );
}

int wxListBox::FindString( const wxString &item, bool bCase ) const {
  wxCHECK_MSG( m_treeview != NULL, wxNOT_FOUND, wxT( "invalid listbox" ) );
  //Sort of hackish - maybe there is a faster way
  unsigned int nCount = wxListBox::GetCount();
  for( unsigned int i = 0; i < nCount; ++i ) {
    if( item.IsSameAs( wxListBox::GetString( i ), bCase ) ) {
      return ( int )i;
    }
  }
  // it's not an error if the string is not found -> no wxCHECK
  return wxNOT_FOUND;
}

// ----------------------------------------------------------------------------
// selection
// ----------------------------------------------------------------------------

int wxListBox::GetSelection() const {
  wxCHECK_MSG( m_treeview != NULL, wxNOT_FOUND, wxT( "invalid listbox" ) );
  wxCHECK_MSG( HasFlag( wxLB_SINGLE ), wxNOT_FOUND,
               wxT( "must be single selection listbox" ) );
  GtkTreeIter iter;
  GtkTreeSelection* selection = gtk_tree_view_get_selection( m_treeview );
  // only works on single-sel
  if( !gtk_tree_selection_get_selected( selection, NULL, &iter ) ) {
    return wxNOT_FOUND;
  }
  GtkTreePath* path =
    gtk_tree_model_get_path( GTK_TREE_MODEL( m_liststore ), &iter );
  int sel = gtk_tree_path_get_indices( path )[0];
  gtk_tree_path_free( path );
  return sel;
}

int wxListBox::GetSelections( wxArrayInt& aSelections ) const {
  wxCHECK_MSG( m_treeview != NULL, wxNOT_FOUND, wxT( "invalid listbox" ) );
  aSelections.Empty();
  int i = 0;
  GtkTreeIter iter;
  GtkTreeSelection* selection = gtk_tree_view_get_selection( m_treeview );
  if( gtk_tree_model_get_iter_first( GTK_TREE_MODEL( m_liststore ), &iter ) ) {
    //gtk_tree_selection_get_selected_rows is GTK 2.2+ so iter instead
    do {
      if( gtk_tree_selection_iter_is_selected( selection, &iter ) ) {
        aSelections.Add( i );
      }
      i++;
    } while( gtk_tree_model_iter_next( GTK_TREE_MODEL( m_liststore ), &iter ) );
  }
  return aSelections.GetCount();
}

bool wxListBox::IsSelected( int n ) const {
  wxCHECK_MSG( m_treeview != NULL, false, wxT( "invalid listbox" ) );
  GtkTreeSelection* selection = gtk_tree_view_get_selection( m_treeview );
  GtkTreeIter iter;
  gboolean res = gtk_tree_model_iter_nth_child(
                   GTK_TREE_MODEL( m_liststore ),
                   &iter, NULL, //NULL = parent = get first
                   n );
  wxCHECK_MSG( res, false, wxT( "Invalid index" ) );
  return gtk_tree_selection_iter_is_selected( selection, &iter );
}

void wxListBox::DoSetSelection( int n, bool select ) {
  // passing -1 to SetSelection() is documented to deselect all items
  if( n == wxNOT_FOUND ) {
    // ... and not generate any events in the process
    GtkDeselectAll();
    return;
  }
  wxCHECK_RET( IsValid( n ), wxT( "invalid index in wxListBox::SetSelection" ) );
  // don't generate the selection event
  GtkSetSelection( n, select, true );
}

void wxListBox::GtkDeselectAll() {
  wxCHECK_RET( m_treeview != NULL, wxT( "invalid listbox" ) );
  GtkTreeSelection* selection = gtk_tree_view_get_selection( m_treeview );
  m_blockEvent = true;
  gtk_tree_selection_unselect_all( selection );
  m_blockEvent = false;
}

void wxListBox::GtkSetSelection( int n, const bool select, const bool blockEvent ) {
  wxCHECK_RET( m_treeview != NULL, wxT( "invalid listbox" ) );
  GtkTreeSelection* selection = gtk_tree_view_get_selection( m_treeview );
  GtkTreeIter iter;
  gboolean res = gtk_tree_model_iter_nth_child(
                   GTK_TREE_MODEL( m_liststore ),
                   &iter, NULL, //NULL = parent = get first
                   n
                 );
  wxCHECK_RET( res, wxT( "Invalid index" ) );
  m_blockEvent = blockEvent;
  if( select ) {
    gtk_tree_selection_select_iter( selection, &iter );
  } else
  { gtk_tree_selection_unselect_iter( selection, &iter ); }
  GtkTreePath* path = gtk_tree_model_get_path(
                        GTK_TREE_MODEL( m_liststore ), &iter );
  gtk_tree_view_scroll_to_cell( m_treeview, path, NULL, FALSE, 0.0f, 0.0f );
  gtk_tree_path_free( path );
  m_blockEvent = false;
}

void wxListBox::DoScrollToCell( int n, float alignY, float alignX ) {
  wxCHECK_RET( m_treeview, wxT( "invalid listbox" ) );
  wxCHECK_RET( IsValid( n ), wxT( "invalid index" ) );
  //RN: I have no idea why this line is needed...
  if( gdk_pointer_is_grabbed() && GTK_WIDGET_HAS_GRAB( m_treeview ) ) {
    return;
  }
  GtkTreeIter iter;
  gtk_tree_model_iter_nth_child(
    GTK_TREE_MODEL( m_liststore ),
    &iter,
    NULL, //NULL = parent = get first
    n
  );
  GtkTreePath* path = gtk_tree_model_get_path(
                        GTK_TREE_MODEL( m_liststore ), &iter );
  // Scroll to the desired cell (0.0 == topleft alignment)
  gtk_tree_view_scroll_to_cell( m_treeview, path, NULL,
                                TRUE, alignY, alignX );
  gtk_tree_path_free( path );
}

void wxListBox::DoSetFirstItem( int n ) {
  DoScrollToCell( n, 0, 0 );
}

void wxListBox::EnsureVisible( int n ) {
  DoScrollToCell( n, 0.5, 0 );
}

// ----------------------------------------------------------------------------
// hittest
// ----------------------------------------------------------------------------

int wxListBox::DoListHitTest( const wxPoint& point ) const {
  // gtk_tree_view_get_path_at_pos() also gets items that are not visible and
  // we only want visible items we need to check for it manually here
  if( !GetClientRect().Contains( point ) ) {
    return wxNOT_FOUND;
  }
  // need to translate from master window since it is in client coords
  gint binx, biny;
  gdk_window_get_geometry( gtk_tree_view_get_bin_window( m_treeview ),
                           &binx, &biny, NULL, NULL, NULL );
  GtkTreePath* path;
  if( !gtk_tree_view_get_path_at_pos
      (
        m_treeview,
        point.x - binx,
        point.y - biny,
        &path,
        NULL,   // [out] column (always 0 here)
        NULL,   // [out] x-coord relative to the cell (not interested)
        NULL    // [out] y-coord relative to the cell
      ) ) {
    return wxNOT_FOUND;
  }
  int index = gtk_tree_path_get_indices( path )[0];
  gtk_tree_path_free( path );
  return index;
}

// ----------------------------------------------------------------------------
// helpers
// ----------------------------------------------------------------------------

#if wxUSE_TOOLTIPS
void wxListBox::ApplyToolTip( GtkTooltips *tips, const wxChar *tip ) {
  // RN: Is this needed anymore?
  gtk_tooltips_set_tip( tips, GTK_WIDGET( m_treeview ), wxGTK_CONV( tip ), ( gchar* ) NULL );
}
#endif // wxUSE_TOOLTIPS

GtkWidget *wxListBox::GetConnectWidget() {
  // the correct widget for listbox events (such as mouse clicks for example)
  // is m_treeview, not the parent scrolled window
  return GTK_WIDGET( m_treeview );
}

GdkWindow *wxListBox::GTKGetWindow( wxArrayGdkWindows& ( windows ) ) const {
  return gtk_tree_view_get_bin_window( m_treeview );
}

void wxListBox::DoApplyWidgetStyle( GtkRcStyle *style ) {
  if( m_hasBgCol && m_backgroundColour.Ok() ) {
    GdkWindow *window = gtk_tree_view_get_bin_window( m_treeview );
    if( window ) {
      m_backgroundColour.CalcPixel( gdk_drawable_get_colormap( window ) );
      gdk_window_set_background( window, m_backgroundColour.GetColor() );
      gdk_window_clear( window );
    }
  }
  gtk_widget_modify_style( GTK_WIDGET( m_treeview ), style );
}

wxSize wxListBox::DoGetBestSize() const {
  wxCHECK_MSG( m_treeview, wxDefaultSize, wxT( "invalid tree view" ) );
  // Start with a minimum size that's not too small
  int cx, cy;
  GetTextExtent( wxT( "X" ), &cx, &cy );
  int lbWidth = 0;
  int lbHeight = 10;
  // Find the widest string.
  const unsigned int count = GetCount();
  if( count ) {
    int wLine;
    for( unsigned int i = 0; i < count; i++ ) {
      GetTextExtent( GetString( i ), &wLine, NULL );
      if( wLine > lbWidth ) {
        lbWidth = wLine;
      }
    }
  }
  lbWidth += 3 * cx;
  // And just a bit more for the checkbox if present and then some
  // (these are rough guesses)
  #if wxUSE_CHECKLISTBOX
  if( m_hasCheckBoxes ) {
    lbWidth += 35;
    cy = cy > 25 ? cy : 25; // rough height of checkbox
  }
  #endif
  // Add room for the scrollbar
  lbWidth += wxSystemSettings::GetMetric( wxSYS_VSCROLL_X );
  // Don't make the listbox too tall but don't make it too small neither
  lbHeight = ( cy + 4 ) * wxMin( wxMax( count, 3 ), 10 );
  wxSize best( lbWidth, lbHeight );
  CacheBestSize( best );
  return best;
}

// static
wxVisualAttributes
wxListBox::GetClassDefaultAttributes( wxWindowVariant ( variant ) ) {
  return GetDefaultAttributesFromGTKWidget( gtk_tree_view_new, true );
}

#endif // wxUSE_LISTBOX
